home *** CD-ROM | disk | FTP | other *** search
/ Floppyshop 2 / Floppyshop - 2.zip / Floppyshop - 2.iso / art&graf.ix / art-4542 / rshade30 / primitiv.e_m < prev    next >
Text File  |  1993-09-06  |  22KB  |  664 lines

  1.  
  2. .\"
  3. .\" Brief tutorial on adding new primitives to rayshade.
  4. .\" Craig Kolb 10/89
  5. .\"
  6. .\" $Id: primitive.ms,v 3.0.1.1 90/04/10 17:22:36 craig Exp $
  7. .\"
  8. .\" $Log:    primitive.ms,v $
  9. .\" Revision 3.0.1.1  90/04/10  17:22:36  craig
  10. .\" patch5: addscaledvec() no longer returns a structure...
  11. .\" 
  12. .\" Revision 3.0  89/10/24  18:07:30  craig
  13. .\" Baseline for first official release.
  14. .\" 
  15. .de D(
  16. .DS
  17. .nr PS 8 
  18. .ps 8 
  19. .nr VS 12p
  20. .vs 12p
  21. .cs 1 20 
  22. ..
  23. .de D)
  24. .DE
  25. .nr PS 10
  26. .ps 10
  27. .nr VS 18p
  28. .vs 18p
  29. .cs 1
  30. ..
  31. .DS
  32. .ND October, 1989
  33. .RP
  34. .de ]H
  35. .nr PS 10
  36. .ps 10
  37. 'sp |0.5i-1
  38. .tl 'Rayshade Primitive Tutorial'-%-'Draft'
  39. .ps
  40. .ft
  41. 'sp |1.0i
  42. .ns
  43. ..
  44. .wh0 ]H
  45. .pn 2
  46. .LP
  47. .TL
  48. Adding Primitives to Rayshade
  49. .TL
  50. \fR\fIDraft\fR
  51. .AU
  52. Craig Kolb
  53. .AI
  54. Department of Mathematics
  55. Yale University
  56. New Haven, CT  06520
  57. .sp .5i
  58. .nr VS 18pts
  59. .PP
  60. This tutorial describes the process of adding new primitives to
  61. rayshade.  Although the hooks for adding primitives are relatively
  62. straight-forward, it is difficult to see the "big picture" by 
  63. studying the source code.  While this tutorial is primarily
  64. meant for those interested getting their hands dirty, it
  65. provides a overview of the design of at least one portion
  66. of rayshade and thus may be of interest even if you are not
  67. planning on making modifications.
  68. .LP
  69. Adding a new primitive involves modifying at least six source
  70. files and creating at least one new file:
  71. .NH
  72. primobj.h
  73. .LP
  74. A datatype for the new primitive is added, and a pointer for
  75. the new type is added to the Primitive type.
  76. .NH
  77. constants.h
  78. .LP
  79. A numerical type is reserved for the primitive.
  80. .NH
  81. intersect.c
  82. .LP
  83. Various arrays of pointers to functions are modified to include
  84. new functions which will be written for the new primitive.
  85. The name of the new primitive is added to a list of names,
  86. and an array of flags is modified to reflect the addition of
  87. the new primitive.
  88. .NH
  89. funcdefs.h
  90. .LP
  91. The ray-primitive intersection function, primitive creation
  92. function, normal calculation function, and bounding-box 
  93. calculation function are declared.
  94. .NH
  95. <primitive-type>.c
  96. .LP
  97. This file contains
  98. all of the code needed to perform
  99. ray-primitive intersection tests, find normals, and compute
  100. bounding boxes for the new primitive type.
  101. .NH
  102. input_lex.l
  103. .LP
  104. The keyword used to identify the new primitive is added to
  105. the list of recognized keywords.
  106. .NH
  107. input_yacc.y
  108. .LP
  109. The new primitive is added to the list of primitives recognized by
  110. the yacc grammar.  The added code will call the routine
  111. which creates and returns a reference to the primitive
  112. which was defined in <primitive-type>.c
  113. .LP
  114. In addition, the Makefile must be updated to reflect the existence of the
  115. new source file.
  116. .sp 1
  117. .LP
  118. In this tutorial, a new primitive
  119. .I disc,
  120. is added to rayshade.  A disc is specified by its center, radius,
  121. and normal.
  122. .br
  123. .NR PS 12
  124. .ps 12
  125. .sp .5
  126. \fBThe Primitive type\fR
  127. .nr PS 10
  128. .ps 10
  129. .sp .5
  130. .LP
  131. All Primitives in rayshade are referenced using a single Primitive structure.
  132. This structure is defined as:
  133. .D(
  134. typedef struct Primitive {
  135.         char type;                      /* object type */
  136.         struct Surface *surf;           /* default surface */
  137.         union {
  138.                 Sphere          *p_sphere;
  139.                 Box             *p_box;
  140.                 Triangle        *p_triangle;
  141.                 Superq          *p_superq;
  142.                 Plane           *p_plane;
  143.                 Cylinder        *p_cylinder;
  144.                 Polygon         *p_poly;
  145.                 Cone            *p_cone;
  146.                 Hf              *p_hf;
  147.         } objpnt;                      /* Pointer to primitive */
  148. } Primitive;
  149. .D)
  150. .LP
  151. The
  152. .I type
  153. field is used by various routines in intersect.c to determine
  154. which elements in a number of arrays should be used when dealing with
  155. a given Primitive structure.
  156. The
  157. .I surf
  158. field points to the surface associated with the primitive.
  159. The
  160. .I objpnt
  161. field is a union of pointers to different primitive types.
  162. The
  163. .I type
  164. of the Primitive is used to determine which pointer to
  165. dereference.
  166. .NH 0
  167. Modifying primobj.h
  168. .LP
  169. Primobj contains structures describing each primitive type.  For example,
  170. a sphere is defined as:
  171. .D(
  172. typedef struct {
  173.         double r;               /* radius   */
  174.         double x, y, z;         /* position */
  175. } Sphere;
  176. .D)
  177. We must define a similar structure for the disc primitive.  After the
  178. definition of the Hf type, we add:
  179. .D(
  180. /*
  181.  * Disc
  182.  */
  183. typedef struct {
  184.         Vector  center,         /* Center of the disc */
  185.                 normal;         /* Normal to disc */
  186.         double  d,              /* Plane constant */
  187.                 radius;         /* Radius of disc */
  188. } Disc;
  189. .D)
  190. .LP
  191. We must also add a pointer for the Disc type to the Primitive type.
  192. So, we add a line to the \fIobjpnt\fR union in the Primitive definition,
  193. giving us: 
  194. .D(
  195. typedef struct Primitive {
  196.         char type;             /* object type */
  197.         struct Surface *surf;  /* default surface */
  198.         union {
  199.                 Sphere          *p_sphere;
  200.                 Box             *p_box;
  201.                 Triangle        *p_triangle;
  202.                 Superq          *p_superq;
  203.                 Plane           *p_plane;
  204.                 Cylinder        *p_cylinder;
  205.                 Polygon         *p_poly;
  206.                 Cone            *p_cone;
  207.                 Hf              *p_hf;
  208.                 Disc            *p_disc;
  209.         } objpnt;             /* Pointer to primitive */
  210. } Primitive;
  211. .D)
  212. .NH
  213. Modifying constants.h 
  214. .LP
  215. In constants.h, there are a series of lines resemblin:
  216. .D(
  217. #define SPHERE          0
  218. #define BOX             1
  219. #define TRIANGLE        2
  220. #define SUPERQ          3
  221. #define PLANE           4
  222. #define CYL             5
  223. #define POLY            6
  224. #define PHONGTRI        7
  225. #define CONE            8
  226. #define HF              9
  227. #define LIST            10
  228. #define GRID            11
  229. .D)
  230. .LP
  231. These lines define the values assigned to the 'type' field in each
  232. Primitive and Object.  (Actually, a Primitive can never be of type
  233. LIST or GRID, but an Object may be.)  We must add a similar line for
  234. the new primitive.
  235. .LP
  236. When adding new values, we
  237. .I must
  238. add the primitive
  239. .I before
  240. the
  241. lines defining the types for LIST and GRID.  This is due to the way
  242. arrays are indexed in intersect.c  So, below the HF type, we add a line
  243. for the Disc type and increment the LIST and GRID types:
  244. .D(
  245. #define CONE            8
  246. #define HF              9
  247. #define DISC            10
  248. #define LIST            11
  249. #define GRID            12
  250. .D)
  251. We must also increment PRIMTYPES, the total number of primitives types:
  252. .D(
  253. #define PRIMTYPES       11
  254. .D)
  255. .NH
  256. Modifying intersect.c
  257. .LP
  258. In intersect.c, several arrays are declared and initialized which are indexed
  259. by Primitive or Object type.  We must modify these array declarations
  260. to reflect the addition of the new primitive type.  The first array to
  261. be modified is :
  262. .D(
  263. double (*objint[])() = {intsph, intbox, inttri, intsup, intplane, intcyl,
  264.                         intpoly, inttri, intcone, inthf};
  265. .D)
  266. This array of pointers to functions contains the names of the functions
  267. which perform ray/primitive intersection tests.  Here,
  268. .I intsph,
  269. the
  270. 0th element in the array, is the name of the intersection routine
  271. for the Sphere primitive, which is declared as type 0 in constants.h.
  272. Similarly,
  273. .I intplane
  274. is the 4th element in the array, and the Plane
  275. primitive is defined to be type 4.  This is due to the fact that a
  276. Primitive's type field is used as an index into this array.
  277. .LP
  278. So, we must add the name of the new ray/disc intersection test, which
  279. we will name "intdisc", to the objint[] array:
  280. .D(
  281. double (*objint[])() = {intsph, intbox, inttri, intsup, intplane, intcyl,
  282.                         intpoly, inttri, intcone, inthf, intdisc};
  283. .D)
  284. Similarly, we must modify the objnrm[] and objextent[] arrays to contain
  285. the names of the functions that compute a specific disc's normal and
  286. bounding box:
  287. .D(
  288. int (*objnrm[])() = {nrmsph, nrmbox, nrmtri, nrmsup, nrmplane, nrmcyl,
  289.                      nrmpoly, nrmtri, nrmcone, nrmhf, nrmdisc};
  290.  
  291. int (*objextent[])() = {sphextent, boxextent, triextent, supextent,
  292.                         planeextent, cylextent, polyextent, triextent,
  293.                         coneextent, hfextent, discextent};
  294. .D)
  295. .LP
  296. In addition, there is an array of Primitive names which is used when
  297. printing statistics.  Again, we need to modify the array to include
  298. the name of the Disc primitive:
  299. .D(
  300. char *primnames[PRIMTYPES] = {  "Sphere", "Box", "Triangle", "Superq", "Plane",
  301.                                 "Cylinder", "Polygon", "Phongtri", "Cone",
  302.                                 "Heightfield", "Disc"};
  303. .D)
  304. .LP
  305. Lastly, there is an array of flags named CheckBounds[] which is indexed
  306. by Object type.  This array is used by intersect() to determine if
  307. a ray/bounding-box intersection test should be performed before a ray/Object
  308. intersection test.  If the element in CheckBounds[] corresponding to
  309. an Object's type is TRUE, a ray/Object intersection test will only occur
  310. if a ray/bounding-box intersection test succeeds.  (Even if ray/bounding-box
  311. tests are not performed for a given primitive, the bounding box is still
  312. used to determine the extent of the object when it is included in compound 
  313. Objects.)
  314. .LP
  315. We will set the flag corresponding to the Disc primitive to be TRUE.   Note
  316. that the CheckBounds array is indexed by \fIObject\fR type, so we add the
  317. entry for the Disc type before the final two entries in the array:
  318. .D(
  319. char CheckBounds[] = {TRUE, FALSE, TRUE, FALSE, FALSE, TRUE, TRUE, TRUE,
  320.                       TRUE, FALSE, TRUE, FALSE, FALSE};
  321. .D)
  322. .NH
  323. Modifying funcdefs.h
  324. .LP
  325. The file funcdefs.h contains a number of function declarations.  We must add
  326. declarations for the four new functions to be written.  Firstly,
  327. we add the declaration of "nrmdisc" to the normal-finding functions:
  328. .D(
  329. int    nrmsph(), nrmbox(), nrmtri(), nrmsup(),nrmplane(), nrmcyl(),
  330.         nrmpoly(), nrmcone(), nrmhf(), nrmdisc();
  331. .D)
  332. .LP
  333. Next, we declare "intdisc" with the other ray/primitive intersection
  334. test functions:
  335. .D(
  336. /*
  337.  * Intersection routines
  338.  */
  339. double  intsph(), intbox(), inttri(), intsup(),intplane(), crossp(), intcyl(),
  340.           intpoly(), intcone(), inthf(), intdisc();
  341. .D)
  342. And the bounding-box routine:
  343. .D(
  344. /*
  345.  * Extent-box finding routines
  346.  */
  347. int     sphextent(),boxextent(),triextent(),supextent(),planeextent(),
  348.         cylextent(), polyextent(), coneextent(), hfextent(),
  349.         discextent();
  350. .D)
  351. .LP
  352. And lastly, we add "makdisc", the routine which will create a reference
  353. to a particular disc, to the list of object creation functions:
  354. .D(
  355. /*
  356.  * Object creation routines
  357.  */
  358. Object *maksph(), *makbox(), *maktri(), *maksup(), *makplane(), *makcyl(),
  359.          *makpoly(), *makcone(), *makhf(), *makdisc(), *new_object()
  360. .D)
  361. .NH
  362. Writing disc.c
  363. .LP
  364. And now we must write the four functions makdisc(), intdisc(), nrmdisc()
  365. and discextent().  The first thing you should do is copy
  366. the Copyright
  367. template to the top of disc.c.
  368. .LP
  369. Next, we need to #include the necessary header files.  In addition to math.h
  370. and stdio.h, you will need "constants.h" (which includes the definition
  371. of DISC), "typedefs.h" (which includes all sorts of useful structure
  372. definitions), and "funcdefs.h" (which includes all sorts of useful
  373. function declarations).
  374. .NH 2
  375. makdisc()
  376. .LP
  377. Every primitive-creation function must take a least one argument -- the
  378. name of the surface to be associated with the primitive.  Besides this
  379. argument, these functions will be passed any parameters needed to define
  380. a particular primitive.  For us, this means two vectors (the center of
  381. the disc and its normal) and one double (the radius of the disc).
  382. .LP
  383. The makdisc() routine will do several things.  In addition to creating a new
  384. Disc, it must allocate a Primitive structure and set its fields
  385. appropriately -- it must point to the new Disc, have its "type" field
  386. set correctly, and it must point to the surface associated with the
  387. primitive.  In addition, an Object which points to this new Primitive
  388. must be created and initialized properly.  It is this Object structure
  389. which is returned by makdisc().
  390. .LP
  391. So, we write:
  392. .D(
  393. Object *
  394. makdisc(surf, center, radius, norm)
  395. char *surf;
  396. Vector center, norm;
  397. double radius;
  398. {
  399.         Disc *disc;             /* Pointer to new disc. */
  400.         Primitive *prim;        /* Pointer to new Primitive */
  401.         Object *newobj;         /* Pointer to new Object */
  402.         Vector tmpnorm;         /* normalized normal */
  403.         extern int Quiet;       /* True if we shouldn't complain */
  404.         extern int yylineno;    /* Current line # in input file. */
  405.  
  406.         if (radius < EPSILON) {
  407.                 if (!Quiet)
  408.                         fprintf(stderr,"Degenerate disc (line %d).\\n",
  409.                                         yylineno);
  410.                 /*
  411.                  * Don't create this primitive.
  412.                  */
  413.                 return (Object *)0;
  414.         }
  415.  
  416.         tmpnorm = norm;
  417.         if (normalize(&tmpnorm) == 0.) {
  418.                 if (!Quiet)
  419.                         fprintf(stderr,"Degenerate disc normal (line %d).\\n",
  420.                                         yylineno);
  421.                 return (Object *)0;
  422.         }
  423.         /*
  424.          * Allocate new Disc.
  425.          */
  426.         disc = (Disc *)Malloc(sizeof(Disc));
  427.         /*
  428.          * Initialize new disc.
  429.          * We store the square of the radius to save us a sqrt().
  430.          */
  431.         disc->radius = radius*radius;
  432.         disc->center = center;
  433.         disc->normal = tmpnorm;
  434.         /*
  435.          * Compute plane constant.
  436.          */
  437.         disc->d = dotp(¢er, &tmpnorm);
  438.         /*
  439.          * Allocate new Primitive
  440.          */
  441.         prim = mallocprim();
  442.         /*
  443.          * Set Primitive type and pointer to new Disc.
  444.          */
  445.         prim->type = DISC;
  446.         prim->objpnt.p_disc = disc;
  447.         /*
  448.          * Search for named surface in list of defined surfaces.
  449.          * find_surface() will exit if the surface is not found.
  450.          */
  451.         prim->surf = find_surface(surf);
  452.         /*
  453.          * Create and return new object with NULL name, of type DISC,
  454.          * which points to the new Primitive and has no transformation
  455.          * associated with it.
  456.          */
  457.         return new_object(NULL, DISC, (char *)prim, (Trans *)NULL);
  458. }
  459. .D)
  460. .LP
  461. In this case, our primitive creation function is straight-forward.  In
  462. some cases, in order to facilitate ray-primitive intersection tests,
  463. a more general version of the primitive is created, and a transformation
  464. matrix is computed to transform the generic primitive to the specific
  465. primitive requested by the user.  Then, one need only perform
  466. intersection tests against the generic version of the primitive.  Examples
  467. of these types of primitives are the cone and cylinder.  The generic
  468. versions of both of these primitives have their main axes coincident
  469. with the Z axis and their base at the origin.  Transformations are
  470. computed in makcyl() and makcone() to transform the generic case to
  471. the specific case.  See "cone.c", "cylinder.c" and "input_yacc.y" for
  472. details.
  473. .NH 2
  474. intdisc()
  475. .LP
  476. Each primitive/ray intersection routine is passed three values:
  477. a pointer to a Primitive, the origin of the ray, and the direction of
  478. the ray.  Each intersection function must do two things.  Firstly,
  479. it must increment an element in the primtests[] array to reflect the
  480. fact that a ray/primitive intersection test has occurred.  Most importantly,
  481. each function must return the distance from the ray origin along the
  482. ray direction to the closest point of ray/primitive intersection.  This
  483. distance must be greater than EPSILON.  If it is less than EPSILON, it is
  484. assumed that no intersection occurs between the ray and the given primitive.
  485. If not valid intersection exists, 0 is returned.
  486. .LP
  487. So, we write:
  488. .D(
  489. double
  490. intdisc(pos, ray, obj)
  491. Vector *pos, *ray;
  492. Primitive *obj;
  493. {
  494.         Disc *disc;
  495.         Vector hit;
  496.         double denom, dist;
  497.         extern unsigned long primtests[];
  498.  
  499.         primtests[DISC]++;
  500.         disc = obj->objpnt.p_disc;
  501.  
  502.         denom = dotp(&disc->normal, ray);
  503.         if (denom == 0.)
  504.                 return 0.;
  505.         dist = (disc->d - dotp(&disc->normal, pos)) / denom;
  506.         if (dist > FAR_AWAY || dist < EPSILON)
  507.                 return 0.;
  508.         /*
  509.          *  Find difference between point of intersection and center of disc.
  510.          */
  511.         addscaledvec(*pos, dist, *ray, &hit);
  512.         vecsub(hit, disc->center, &hit);
  513.         /*
  514.          * If hit point is <= disc->radius from center, we've hit the disc.
  515.          */
  516.         if (dotp(&hit, &hit) <= disc->radius)
  517.                 return dist;
  518.         return 0.;
  519. }
  520. .D)
  521. .NH 2
  522. nrmdisc()
  523. .LP
  524. Each primitive normal routine is passed a location to a primitive, a pointer
  525. to a point on the surface of the primitive, and a pointer to a vector
  526. which must be set to the normal to the primitive at the point of intersection.
  527. For the disc, this is very simple, as the disc is planar:
  528. .D(
  529. Vector
  530. nrmdisc(pos, prim, nrm)
  531. Vector *pos, *nrm;
  532. Primitive *prim;
  533. {
  534.         *nrm = prim->objpnt.p_disc->normal;
  535. }
  536. .D)
  537. .NH 2
  538. discextent()
  539. .LP
  540. The discextent() routine is passed a pointer to a disc Primitive as well
  541. as a bounding
  542. box stored as a 2 by 3 array of doubles.  The routine computes the extent
  543. of the disc along each axis and fills the bounding box array appropriately.
  544. .LP
  545. Note that the bounding box of all primitives should be at least
  546. 2*EPSILON along each axis to avoid problems with roundoff error.
  547. Fortunately, primextent(),
  548. routine which calls the primitive bounding box functions, will check
  549. for and "widen" degenerate bounding boxes.
  550. Thus, the
  551. bounding box volumes are allowed to compute degenerate boxes.
  552. Also, if a primitive is unbounded (e.g., a plane),
  553. the maximum X extent should be set to be less than the minimum X extent.
  554. .LP
  555. So, discextent is written as:
  556. .D(
  557. discextent(prim, bounds)
  558. Primitive *prim;
  559. double bounds[2][3];
  560. {
  561.         Disc *disc;
  562.         double extent, rad;
  563.  
  564.         disc = prim->objpnt.p_disc;
  565.  
  566.         rad = sqrt(disc->radius);
  567.         /*
  568.          * Project disc along each of X, Y and Z axes.
  569.          */
  570.         extent = 1. - disc->normal.x * disc->normal.x;
  571.         extent *= rad;
  572.         bounds[LOW][X] = disc->center.x - extent;
  573.         bounds[HIGH][X] = disc->center.x + extent;
  574.         extent = 1. - disc->normal.y * disc->normal.y;
  575.         extent *= rad;
  576.         bounds[LOW][Y] = disc->center.y - extent;
  577.         bounds[HIGH][Y] = disc->center.y + extent;
  578.         extent = 1. - disc->normal.z * disc->normal.z;
  579.         extent *= rad; 
  580.         bounds[LOW][Z] = disc->center.z - extent;
  581.         bounds[HIGH][Z] = disc->center.z + extent;
  582. }
  583. .D)
  584. .NH
  585. input_lex.l
  586. .LP
  587. Among other things, input_lex.l contains a list of all the keywords
  588. recognized by rayshade.  To this list we must add the keyword for
  589. the new primitive type.  Following the example of other keywords,
  590. we add:
  591. .D(
  592. disc                            {return(tDISC);}
  593. .D)
  594. .NH
  595. input_yacc.y
  596. .LP
  597. Near the top of input_yacc.y are the declarations for the tokens
  598. returned by lex.  We must add the new token, tDISC, to this list:
  599. .D(
  600. %token tBACKGROUND tBLOTCH tBOX tBUMP tCONE tCYL tDIRECTIONAL tDISC
  601. .D)
  602. Finally, we need to add the production to the yacc grammar which will
  603. call makdisc().  We first modify the Primtype production to include
  604. a new production named Disc:
  605. .D(
  606. Primtype        : Plane
  607.                 | Sphere
  608.                 | Box
  609.                 | Triangle
  610.                 | Cylinder
  611.                 | Cone
  612.                 | Superq
  613.                 | Poly
  614.                 | HeightField
  615.                 | Disc
  616.                 ;
  617. .D)
  618. .LP
  619. Next, near the productions for the other primitives, we add:
  620. .D(
  621. Disc            : tDISC tSTRING Vector Fnumber Vector
  622.                 {
  623.                         /*
  624.                          * disc surfname center.x center.y center.z
  625.                          * radius norm.x norm.y norm.z
  626.                          */
  627.                         LastObj = maksph($2, $3, $4, $5);
  628.                 }
  629.                 ;
  630. .D)
  631. .NH
  632. Testing
  633. .LP
  634. Once you add "disc.o" to the OBJS list in the Makefile, you should be
  635. able to re-compile rayshade.  A good input file for testing the disc
  636. primitive might be:
  637. .D(
  638. /*
  639.  * Demo picture of a textured ground plane with a sphere, cone
  640.  * and disc.  The disc is situated on the top of the cone and faces
  641.  * up and towards the viewer.
  642.  */
  643. eyep 0. 25. 7.
  644. screen 256 256
  645. light 1.4 point -15. 20. 15.
  646. surface red .02 0 0 .5 0 0 .2 .2 .2 32. 0.0 0 0
  647. surface blacktile 0.01 0.015 0.01 0.02 0.03 0.02 0.3 0.35 0.3 30 0. 0 0
  648. surface white .02 .02 .008 .5 .5 .25 0.8 0.8 0.8 18 0. 0 0
  649. surface glass 0.02 0.02 0.02  0. 0. 0.  0.8 0.8 0.8  25 0. 0. 1.15
  650.  
  651. disc white -5. 3. 4. 3. 0. 1. 1.
  652. sphere red 4. 3 0 0
  653. /*
  654.  * Cone actually sticks through ground plane.  This solves problems
  655.  * that arise when the bottom of the cone and the plane are coincident.
  656.  */
  657. cone glass -5. 3 -4.1 -5. 3. 4. 4. 0.
  658.  
  659.  
  660. plane white 0. 0. 1. 0. 0. -4. 
  661.         texture marble scale 4. 4. 4.
  662.         texture checker blacktile translate 0. 0. 0.3 scale 4. 4. 4.
  663. .D)
  664.